package com.wangnian.wangcai.service;

import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wangnian.wangcai.config.Config;
import com.wangnian.wangcai.dao.*;
import com.wangnian.wangcai.service.api.ApiChannel;
import com.wangnian.wangcai.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.LinkedBlockingQueue;

import static com.wangnian.wangcai.config.Config.*;
import static com.wangnian.wangcai.util.DateUtil.getNowDate;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

/**
 * @author wangnian
 */
@Service
public class SyncService {

    private static final Logger logger = LoggerFactory.getLogger(SyncService.class);
    private final DayInfoDao dayInfoDao;
    private final NameInfoDao nameInfoDao;
    private final ChiGuDao chiGuDao;
    
    // 创建一个基于系统核心数的线程池，避免频繁创建和销毁
    private static final ExecutorService threadPool;
    
    static {
        int processors = Runtime.getRuntime().availableProcessors();
        // 核心线程数设为处理器核心数+1，最大线程数设为核心数的2倍
        threadPool = new ThreadPoolExecutor(
            processors + 1,                  // 核心线程数
            Math.max(processors * 2, 10),    // 最大线程数
            60L,                             // 空闲线程存活时间
            TimeUnit.SECONDS,                // 时间单位
            new LinkedBlockingQueue<>(1000), // 使用有界队列避免OOM
            new ThreadPoolExecutor.CallerRunsPolicy()); // 当队列满时，使用调用者线程执行任务
        
        // 添加JVM关闭钩子，确保应用关闭时线程池正确关闭
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            threadPool.shutdown();
            try {
                if (!threadPool.awaitTermination(10, TimeUnit.SECONDS)) {
                    threadPool.shutdownNow();
                }
            } catch (InterruptedException e) {
                threadPool.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }));
    }

    public SyncService(DayInfoDao dayInfoDao, NameInfoDao nameInfoDao, ChiGuDao chiGuDao) {
        this.dayInfoDao = dayInfoDao;
        this.nameInfoDao = nameInfoDao;
        this.chiGuDao = chiGuDao;
    }

    /**
     * 更新同花顺
     */
    public void runNameTHS(String quesion) {
        nameInfoDao.deleteAll();
        HttpRequest httpRequest = HttpRequest.get("http://www.iwencai.com/unifiedwap/unified-wap/result/get-stock-pick");
        httpRequest.form("question", quesion);
        httpRequest.form("secondary_intent", "stock");
        httpRequest.form("perpage", "3000");
        String respName = httpRequest.execute().body();
        JSONArray jsonArray = JSONObject.parseObject(respName).getJSONObject("data").getJSONArray("data");
        logger.info("匹配{}个", jsonArray.size());
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);

            String code = jsonObject.getString("hqCode");
            if (!"000001".equals(code)) {
                String name = jsonObject.getString("股票简称");
                NameInfo nameInfo = new NameInfo();
                nameInfo.setCode(code);
                nameInfo.setName(name);
                nameInfoDao.save(nameInfo);
            }
        }
    }

    /**
     * 更新每天数据 - 优化后的批处理版本
     */
    public void runDay(String startDateStr, String endDateStr) {
        logger.info("启动更新最新的day");
        dayInfoDao.deleteAllInBatch();
        List<NameInfo> nameInfos = nameInfoDao.findAll();
        logger.info("取今天的行情，{},{}", startDateStr, endDateStr);
        
        // 先处理大盘信息
        saveDay(null, startDateStr, endDateStr, false);
        
        // 分批处理所有股票数据，每批次处理20支股票
        List<String> allCodes = nameInfos.stream().map(NameInfo::getCode).collect(java.util.stream.Collectors.toList());
        int batchSize = 20; // 每批处理的股票数量
        
        for (int i = 0; i < allCodes.size(); i += batchSize) {
            // 计算本批次的结束索引
            int endIndex = Math.min(i + batchSize, allCodes.size());
            List<String> batchCodes = allCodes.subList(i, endIndex);
            
            logger.info("处理第{}批股票数据，共{}支", (i/batchSize + 1), batchCodes.size());
            
            // 处理本批次的所有股票
            for (String code : batchCodes) {
                saveDay(code, startDateStr, endDateStr, true);
            }
            
            // 批次处理完成后，显式触发GC回收
            System.gc();
        }
        
        logger.info("更新最新的day完成");
    }

    /**
     * 保存数据 - 优化版本
     * 使用流式处理和更高效的数据结构
     *
     * @param code 股票代码
     * @param startDateStr 开始日期
     * @param endDateStr 结束日期
     * @param watch 是否监控
     */
    public void saveDay(String code, String startDateStr, String endDateStr, boolean watch) {
        try {
            String codeStr;
            if (code == null) {
                codeStr = "1.000001";
                code = "1.000001";
            } else {
                if (CommonUtil.isSh(code)) {
                    codeStr = "1." + code;
                } else {
                    codeStr = "0." + code;
                }
            }
            
            // 构建URL参数
            String path = String.format(
                "http://push2his.eastmoney.com/api/qt/stock/kline/get?secid=%s&fields1=f1%%2Cf2%%2Cf3%%2Cf4%%2Cf5&fields2=f51%%2Cf52%%2Cf53%%2Cf54%%2Cf55%%2Cf56%%2Cf57%%2Cf58&klt=101&fqt=1&beg=%s&end=%s",
                codeStr, startDateStr, endDateStr);
            
            String str = HttpRequest.get(path).execute().body();
            JSONObject jsonObject = JSONObject.parseObject(str);
            JSONObject data = jsonObject.getJSONObject("data");
            
            if (data == null) {
                return;
            }
            
            JSONArray jsonArray = data.getJSONArray("klines");
            if (jsonArray == null || jsonArray.size() < 3) {
                return;
            }
            
            // 预分配容量，避免动态扩容
            final int size = jsonArray.size();
            List<DayInfo> dayInfoList = new ArrayList<>(size);
            
            // 快速处理并收集数据
            String finalCode = code;
            for (int i = 0; i < size; i++) {
                String[] s1 = jsonArray.getString(i).split(",");
                
                DayInfo dayInfo = new DayInfo();
                dayInfo.setCode(finalCode);
                dayInfo.setTime(DateUtil.parseToDate(s1[0], DateUtil.DATEFORMAT_YYYYMMDD));
                
                // 批量设置数值型属性，减少自动装箱/拆箱操作
                try {
                    dayInfo.setOpen(Double.parseDouble(s1[1]));
                    dayInfo.setClose(Double.parseDouble(s1[2]));
                    dayInfo.setHigh(Double.parseDouble(s1[3]));
                    dayInfo.setLow(Double.parseDouble(s1[4]));
                    dayInfo.setVolume(Double.parseDouble(s1[5]));
                } catch (NumberFormatException e) {
                    logger.warn("数据格式错误：{}, 股票：{}", e.getMessage(), finalCode);
                    continue; // 跳过无效数据
                }
                
                dayInfoList.add(dayInfo);
            }
            
            // 只有当有足够的数据时才进行指标计算和保存
            if (dayInfoList.size() >= 3) {
                // 计算技术指标
                Indicators.initIndicators(dayInfoList);
                
                // 批量保存数据
                dayInfoDao.saveAll(dayInfoList);
                
                // 更新名称信息
                if (watch) {
                    NameInfo nameInfo = nameInfoDao.findByCode(finalCode);
                    if (nameInfo != null) {
                        DayInfo lastDayInfo = dayInfoList.get(dayInfoList.size() - 1);
                        setAllow(lastDayInfo, nameInfo);
                        nameInfoDao.save(nameInfo);
                    }
                }
                
                logger.info("成功更新股票数据: {}, 记录数: {}", finalCode, dayInfoList.size());
            }
        } catch (Exception ex) {
            logger.error("更新股票{}数据失败: {}", code, ex.getMessage());
        }
    }

    public void setAllow(DayInfo dayInfo, NameInfo nameInfo) {
        nameInfo.setAllow(dayInfo.isAvgUp());
    }

    /**
     * 同步最新的数据
     *
     * @param dayInfo
     */
    public void syncNewDay(DayInfo dayInfo) {
        try {
            String code = dayInfo.getCode();
            DayInfo newDayInfo = dayInfoDao.findByCodeAndTime(code, dayInfo.getTime());
            if (newDayInfo != null) {
                dayInfo.setId(newDayInfo.id);
            }
            List<DayInfo> dayInfos = dayInfoDao.findByCode(code, Sort.by("time"));
            dayInfos.add(dayInfo);
            double jValue = Indicators.calculateKDJ(dayInfos, 9);
            dayInfo.setJ(jValue);
            dayInfoDao.save(dayInfo);
            //更新监控
            NameInfo nameInfo = nameInfoDao.findByCode(code);
            if (nameInfo != null) {
                nameInfo.setPrice(dayInfo.getClose());
                nameInfo.setZf(dayInfo.getZf());
                nameInfo.setDown(Indicators.zf(dayInfo.getHigh(), dayInfo.getClose()));
                nameInfo.setUp(Indicators.zf(dayInfo.getClose(), dayInfo.getLow()));
                nameInfo.setAvg3Differ(0);
                nameInfoDao.save(nameInfo);
            }
        } catch (Exception ex) {
            logger.error("更新最新数据异常:", ex);
            ChatbotSend.send("更新最新的数据异常,code:" + dayInfo.getCode());
        }
    }

    private static int oldMinutes = -1;

    /**
     * 监控短线做T - 优化后的版本
     */
    public void syncShorHold(int minutes) throws InterruptedException {
        List<ChiGu> chiGuList;
        if (refreshChiGu) {
            //重新刷新持仓数据
            chiGuList = ApiChannel.apiService.position(100);
            if (chiGuList.isEmpty()) {
                ChatbotSend.send("查询数量为0，请检查QMT服务");
                return;
            }
            chiGuDao.deleteAll();
            refreshChiGu = false;
        } else {
            chiGuList = chiGuDao.findAll();
        }
        Set<String> collect = new HashSet<>();
        //每分钟都更新
        if (minutes != oldMinutes) {
            oldMinutes = minutes;
            Set<String> collect1 = chiGuList.stream().map(ChiGu::getCode).collect(toSet());
            collect.addAll(collect1);
        } else {
            Set<String> collect1 = chiGuList.stream().filter(e -> e.getYkZf() < 20).map(ChiGu::getCode).collect(toSet());
            collect.addAll(collect1);
        }
        
        // 限制每批次处理的股票数量，避免一次处理太多
        final int MAX_BATCH_SIZE = 30;
        Set<String> limitedCollect = collect.stream()
                                        .limit(MAX_BATCH_SIZE)
                                        .collect(java.util.stream.Collectors.toSet());
        
        // 使用全局线程池而不是每次创建新的
        List<DayInfo> dayInfoList = HttpUtil.getQmt(limitedCollect);
        List<CompletableFuture<Void>> futures = dayInfoList.stream()
                .map(dayInfo -> CompletableFuture.runAsync(() -> syncNewDay(dayInfo), threadPool))
                .toList();
        
        // 等待所有Future完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        
        // 不再关闭线程池，因为它是静态共享的

        Map<String, Double> thsData = dayInfoList.stream().collect(toMap(DayInfo::getCode, DayInfo::getClose));
        for (ChiGu chiGu : chiGuList) {
            Double price = thsData.get(chiGu.getCode());
            if (price == null || price < 0.01) {
                continue;
            }
            chiGu.setYkMoney(price * chiGu.getCount() - chiGu.getCost() * chiGu.getCount());
            chiGu.setPrice(price);
            chiGu.setYkZf(Indicators.zf(price, chiGu.getCost()));

            if (minutes >= 0) {
                DayInfo dayInfo = dayInfoDao.findByCodeAndTime(chiGu.getCode(), DateUtil.getToday());
                if (dayInfo == null) {
                    continue;
                }
                DayInfo daPan = dayInfoDao.findByCodeAndTime("1.000001", DateUtil.getToday());
                notice(chiGu, dayInfo);
                dt(daPan, chiGu, dayInfo, price);
                t(daPan, chiGu, dayInfo, price);
            }
        }
        if (!refreshChiGu) {
            chiGuDao.saveAll(chiGuList);
        }
    }

    public void notice(ChiGu chiGu, DayInfo dayInfo) {
        //当总盈亏大于10，且持仓金额大于20000，且当天没有做减仓任务，就做减仓
        if (chiGu.getYkZf() > 10 && dayInfo.getJ() > 100 && chiGu.getSumMoney() > 20000) {
            if (!dayStr.contains(chiGu.getCode())) {
                List<TaskUtil.Recording> recordings = TaskUtil.findByCodeAndType(chiGu.getCode(), type1);
                //如果做 T 没任务了
                if (recordings.isEmpty() && dayInfo.getZf() > 2) {
                    int count = getCount(1000, dayInfo);
                    ApiChannel.apiService.sell(chiGu.getCode(), count, "减仓操作:" + chiGu.getName());
                    ChatbotSend.send(chiGu.getName() + "回本并＞2w，自动减仓1手");
                    dayStr.add(chiGu.getCode());
                }
            }
        }
    }


    public void dt(DayInfo daPan, ChiGu chiGu, DayInfo dayInfo, double price) {
        List<TaskUtil.Recording> recordings = TaskUtil.findByCodeAndType(chiGu.getCode(), type1);
        for (TaskUtil.Recording recording : recordings) {
            if (DateUtil.getStringToday().equals(recording.getDate())) {
                //如果当天的任务，不交易，不可能有这种机会
                return;
            }
            if (chiGu.getCount() >= 200) {
                double tZf = Indicators.zf(dayInfo.getClose(), recording.getPrice());
                double expected = 12;
                if (tZf > expected && dayInfo.getJ() > 100) {
                    if (chiGu.getCount() >= recording.getCount()) {
                        ApiChannel.apiService.sell(recording.getCode(), recording.getCount(), recording.getLevel() + "减仓操作:" + chiGu.getName());
                        ChatbotSend.send("减仓,已经上涨" + expected + "点,卖出" + recording.getCount() + "股:" + chiGu.getName());
                    } else {
                        ChatbotSend.send("减仓持股数不够,已经上涨" + expected + "点,卖出" + recording.getCount() + "股:" + chiGu.getName());
                    }
                    TaskUtil.remove(recording);
                }
            }
        }
        if (Config.get(autoDT) != 1 || chiGu.getSumMoney() > Config.get(maxDT)) {
            return;
        }

        double sumMoney = chiGu.getSumMoney();
        int zf = -80;
        if (sumMoney < 10000) {
            zf = -20;
        } else if (sumMoney < 20000) {
            zf = -30;
        } else if (sumMoney < 30000) {
            zf = -40;
        } else if (sumMoney < 40000) {
            zf = -50;
        }

        int zf1 = -80;
        if (sumMoney < 1000) {
            zf1 = -5;
        } else if (sumMoney < 3000) {
            zf1 = -8;
        } else if (sumMoney < 5000) {
            zf1 = -10;
        }
        if (chiGu.getYkZf() < zf1 && (100 * price + chiGu.getSumMoney()) < 5000 && dayInfo.getJ() < 0 && daPan.getJ() < 0) {
            int count = getCount(1000, dayInfo);
            boolean buy = ApiChannel.apiService.buy(chiGu.getCode(), count, price, "");
            if (buy) {
                DateUtil.NowDate nowDate = getNowDate();
                int hours = nowDate.getHours();
                int minutes = nowDate.getMinutes();
                if ((hours == 9 && minutes < 30) || (hours == 14 && minutes > 55)) {
                    return;
                }
                ChatbotSend.send("不满5000的个股，只要跌就补仓:" + chiGu.getName());
            }
        }
        if (chiGu.getYkZf() < zf && chiGu.getCost() > 0 && dayInfo.getJ() < 0 && daPan.getJ() < 0){
            int count = chiGu.getCount() / 100 * 100;
            if (TaskUtil.canMai(zf, chiGu.getCode(), type1, count)) {
                boolean buy = ApiChannel.apiService.buy(chiGu.getCode(), count, price, "");
                if (buy) {
                    ChatbotSend.send("定投-总亏损" + zf + "点买入" + count + "股:" + chiGu.getName());
                    TaskUtil.recording(zf, chiGu.getCode(), chiGu.getName(), type1, count, price);
                } else {
                    if (!dayStr.contains(chiGu.getCode())) {
                        ChatbotSend.send("定投-总亏损" + zf + "点,缺钱" + count * price + "元:" + chiGu.getName());
                        dayStr.add(chiGu.getCode());
                    }
                }
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }

    public int getCount(double sumPrice, DayInfo dayInfo) {
        if (dayInfo.getCode().startsWith("68")) {
            return 200;
        }
        int count = (int) (sumPrice / dayInfo.getClose() / 100) * 100;
        count = Math.max(count, 100);
        return count;
    }

    public void t(DayInfo daPan, ChiGu chiGu, DayInfo dayInfo, double price) {
        //做T操作
        List<TaskUtil.Recording> recordings = TaskUtil.findByCodeAndType(chiGu.getCode(), type2);
        for (TaskUtil.Recording recording : recordings) {
            if (recording.getLevel() < 0 && chiGu.getCount() >= 100 && dayInfo.getJ() > 100) {
                double tZf = Indicators.zf(dayInfo.getClose(), recording.getPrice());
                double expected = 3;
                if (tZf > expected && chiGu.getCount() >= recording.getCount()) {
                    ApiChannel.apiService.sell(recording.getCode(), recording.getCount(), recording.getLevel() + "做T操作:" + chiGu.getName());
                    ChatbotSend.send("做T操作卖出" + chiGu.getName() + "(" + chiGu.getCode() + ")");
                    TaskUtil.remove(recording);
                }
            }
        }
        if (chiGu.getYkZf() < 18 && daPan.getJ() < 0) {
            if (Config.get(Config.autoT) != 1 || chiGu.getSumMoney() > Config.get(maxT)) {
                return;
            }
            if (dayInfo.getZf() <= -1 && dayInfo.getJ() < 0) {
                int level = 0;
                // 如果 跌6-7
                if (dayInfo.getZf() <= -5.8 && dayInfo.getZf() >= -7) {
                    level = -6;
                }
                if (dayInfo.getZf() <= -4.8 && dayInfo.getZf() >= -6) {
                    level = -5;
                }
                if (dayInfo.getZf() <= -3.8 && dayInfo.getZf() >= -5) {
                    level = -4;
                }
                if (dayInfo.getZf() <= -2.8 && dayInfo.getZf() >= -4) {
                    level = -3;
                }
                if (level == 0) {
                    return;
                }
                int count = getCount(1000, dayInfo);
                if (TaskUtil.canMai(level, chiGu.getCode(), type2, count)) {
                    boolean buy = ApiChannel.apiService.buy(chiGu.getCode(), count, price, "当天跌幅" + level + "%点买" + count + "股:" + chiGu.getName());
                    if (buy) {
                        ChatbotSend.send("当天跌幅" + level + "买入" + chiGu.getName() + "(" + chiGu.getCode() + ")");
                        TaskUtil.recording(level, chiGu.getCode(), chiGu.getName(), type2, count, price);
                    }
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    dayStr.add(chiGu.getCode());
                }
            }
        }
    }
}


